package com.iltgcl.echovoicedemo.speaker

import android.media.AudioManager
import android.os.Build
import android.speech.tts.TextToSpeech
import androidx.lifecycle.MutableLiveData
import com.iltgcl.echovoice.client.IEchoVoice
import timber.log.Timber

abstract class EchoSpeakerBase(
    private val audioManager: AudioManager,
    private val echoVoice: IEchoVoice,
    override val usage:Int) : IEchoSpeaker {

    private val _speakerState = MutableLiveData(IEchoSpeaker.STATE_STOP)
    override val speakerState = _speakerState
    override val rangeStart = MutableLiveData<RangeStartInfo>().apply { value = RangeStartInfo("", 0, 0, 0) }

    @Volatile protected var curUtteranceId: String? = null

    var volumeWhenFocusLossTransient:Int = -1

    protected abstract fun requestAudioFocus(audioManager: AudioManager):Int
    protected abstract fun abandonAudioFocus(audioManager: AudioManager):Int

    private val isPause: Boolean
        get() = (speakerState.value == IEchoSpeaker.STATE_PAUSE)

    private val isSpeaking: Boolean
        get() = (speakerState.value == IEchoSpeaker.STATE_SPEAKING)

    private val isStop: Boolean
        get() = (speakerState.value == IEchoSpeaker.STATE_STOP)

    override fun speak(
        text: CharSequence,
        utteranceId: String,
        voiceName: Int,
        speed: Int,
        volume: Int,
        pitch: Int) {
        Timber.d("[speak]Enter:$text!")

        if (!isStop) {
            echoVoice.stop(usage)
        }

        if (requestAudioFocus(audioManager) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            curUtteranceId = utteranceId
            _speakerState.value = IEchoSpeaker.STATE_PREPARE
            echoVoice.speak(text,
                TextToSpeech.QUEUE_FLUSH,
                usage,
                utteranceId,
                voiceName,
                speed,
                volume,
                pitch)
        } else {
            Timber.w("[speak]Ignore because requestAudioFocus fail!")
        }
        Timber.d("[speak]Exit:$text!")
    }

    override fun stop() {
        Timber.w("[stop]Enter:isStop=$isStop")
        if (!isStop) {
            Timber.w("[stop]call echoVoice stop")
            echoVoice.stop(usage)
        }
        handleSpeechEnd()
        rangeStart.postValue(RangeStartInfo("", 0, 0, 0))
        Timber.w("[stop]Exit")
    }

    override fun pause(force: Boolean) {
        Timber.d("[pause]Enter:force=$force, isSpeaking=$isSpeaking, isPause=$isPause")
        if (isSpeaking) {
            echoVoice.pause(usage)
            _speakerState.value = IEchoSpeaker.STATE_PAUSE
            if (force) {
                abandonAudioFocus(audioManager)
                volumeRestore()
            }
        } else {
            Timber.w("[pause]do nothing")
        }
        Timber.d("[pause]Exit:isSpeaking=$isSpeaking, isPause=$isPause")
    }

    override fun resume() {
        Timber.d("[resume]Enter:isSpeaking=$isSpeaking, isPause=$isPause")
        if (isPause) {
            if (requestAudioFocus(audioManager) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                echoVoice.resume(usage)
                _speakerState.value = IEchoSpeaker.STATE_SPEAKING
                Timber.w("[resume]------isSpeaking=$isSpeaking, isPause=$isPause ----")
            } else {
                Timber.w("[resume]Ignore because requestAudioFocus Fail")
            }
        } else {
            Timber.w("[resume]Ignore because not in pause state!")
        }
        Timber.d("[resume]Exit:isSpeaking=$isSpeaking, isPause=$isPause")
    }

    override fun onSpeechStart(utteranceId: String) {
        Timber.w("[onSpeechStart]Enter:$usage, $utteranceId, $curUtteranceId")
        if (utteranceId == curUtteranceId) {
            _speakerState.value = IEchoSpeaker.STATE_SPEAKING
        }
    }

    override fun onSpeechDone(utteranceId: String) {
        if (utteranceId == curUtteranceId) {
            Timber.w("[onSpeechDone]Enter:$usage, $utteranceId")
            handleSpeechEnd()
            rangeStart.postValue(RangeStartInfo("", 0, 0, 0))
        }
    }

    override fun onSpeechStop(utteranceId: String) {
        if (utteranceId == curUtteranceId) {
            Timber.w("[onSpeechStop]Enter:$usage, $utteranceId")
            handleSpeechEnd()
            rangeStart.postValue(RangeStartInfo("", 0, 0, 0))
        }
    }

    override fun onSpeechError(utteranceId: String) {
        if (utteranceId == curUtteranceId) {
            Timber.w("[onSpeechError]Enter:$usage, $curUtteranceId")
            handleSpeechEnd()
            rangeStart.postValue(RangeStartInfo("", 0, 0, 0))
        }
    }

    override fun onRangeStart(rangeStartInfo: RangeStartInfo) {
        if (curUtteranceId == rangeStartInfo.utteranceId) {
            Timber.w("[onRangeStart]Enter:$usage, ${rangeStartInfo.utteranceId}")
            rangeStart.postValue(rangeStartInfo)
        }
    }

    private fun handleSpeechEnd() {
        Timber.d("[handleSpeechEnd]Enter:speakerState= ${speakerState.value}")
        _speakerState.value = IEchoSpeaker.STATE_STOP
        curUtteranceId = null
        abandonAudioFocus(audioManager)
        volumeRestore()
        Timber.d("[handleSpeechEnd]Exit:speakerState= ${speakerState.value}")
    }

    private fun getStreamTypeByUsage(): Int {
        return when(usage) {
            IEchoVoice.USAGE_A11Y -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    Timber.w("[getStreamTypeByUsage]STREAM_ACCESSIBILITY")
                    AudioManager.STREAM_ACCESSIBILITY
                } else {
                    Timber.w("[getStreamTypeByUsage]STREAM_MUSIC")
                    AudioManager.STREAM_MUSIC
                }
            }
            IEchoVoice.USAGE_NOTIFICATION -> {
                Timber.w("[getStreamTypeByUsage]STREAM_NOTIFICATION")
                AudioManager.STREAM_NOTIFICATION
            }
            else ->  {
                Timber.w("[getStreamTypeByUsage]else STREAM_MUSIC")
                AudioManager.STREAM_MUSIC
            }
        }
    }

    fun volumeDown() {
        val streamType = getStreamTypeByUsage()
        volumeWhenFocusLossTransient = audioManager.getStreamVolume(streamType)
        audioManager.setStreamVolume(streamType, volumeWhenFocusLossTransient / 2, 0)
    }

    fun volumeRestore() {
        if (volumeWhenFocusLossTransient > -1) {
            val streamType = getStreamTypeByUsage()
            audioManager.setStreamVolume(streamType, volumeWhenFocusLossTransient, 0)
            volumeWhenFocusLossTransient = -1
        }
    }
}